Skip to content

azure-pipelines: migrate installer release pipeline from GitHub Actions#920

Draft
dscho wants to merge 8 commits into
vfs-2.54.0from
azp
Draft

azure-pipelines: migrate installer release pipeline from GitHub Actions#920
dscho wants to merge 8 commits into
vfs-2.54.0from
azp

Conversation

@dscho
Copy link
Copy Markdown
Member

@dscho dscho commented May 16, 2026

Port #913 and #914 from the vfs-2.53.0 to the vfs-2.54.0 branch. While at it, reorganize the changes into a more concise commit history, including two amend! commits that target the next rebase to a new Git version.

At time of writing, git diff azp vfs-2.53.0 -- .azure-pipelines shows an empty diff, proving that it brings vfs-2.54.0 up to date with the latest changes that made v2.53.0.vfs.0.11 possible.

mjcheetham and others added 8 commits May 16, 2026 12:38
azure-pipelines: add stub release pipeline for Azure

Add a release-pipeline scaffold for microsoft/git on Azure
Pipelines, structured around a prereqs stage, a per-platform
build stage with placeholder jobs, and a release stage that
downloads the build artifacts and publishes them to a draft
GitHub release. The per-platform build, signing, and validation
logic lands in subsequent commits.

The pipeline targets Microsoft-internal 1ES-hosted images
across Windows x64, Windows ARM64, macOS, Ubuntu x64, and
Ubuntu ARM64. Windows and Linux matrix entries carry a
`poolArch` dimension because the 1ES hosted pools select their
image from `hostArchitecture`; an arm64 entry on a default x64
pool would silently grab the wrong image. macOS uses the same
matrix-parameter shape as Windows and Linux, so future macOS
variants drop in the same way an extra Windows toolchain would.

The prereqs stage derives the Git version, tag name, and tag
SHA via resolve-version.sh and exposes them as pipeline
variables for downstream stages to pick up. For that walk to
find tags at all, the prereqs checkout uses fetchDepth: 0 /
fetchTags: true. setup-git-bash.cmd is the Windows-side
prerequisite that prepends Git Bash to PATH, since the bare
hosted image does not provide a bash for Bash@3 tasks to find.

ESRP signing to be added later.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
Assisted-by: Claude Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Bring ESRP code signing into the release pipeline, gated behind
an `esrp` boolean parameter that defaults to false until the rest
of the signing wiring catches up. Microsoft policy precludes
shipping unsigned binaries from this pipeline, so every
per-platform build job needs an obvious place to plug signing in.

The Windows flow runs through a custom esrpsign.sh script rather
than the EsrpCodeSigning ADO task. The custom script gives the
later Windows installer commit a CLI-shaped seam it can register
as Git for Windows' `git signtool` alias from build-extra's
release pipeline, so every binary embedded inside the installer
gets signed rather than only the outer .exe wrapper. The canned
ADO task does not expose that integration point.

The accompanying setup template uses AzureCLI@2 to bind to the
WIF service connection by name rather than by GUID, and relies on
addSpnToEnvironment to surface the service principal ID, tenant
ID, and connection GUID at runtime via ENDPOINT_URL_* env vars.
That way esrpsign.sh composes the auth JSON with no hardcoded
identifiers leaking into the repository. EsrpClientTool@4 takes
care of downloading and caching the ESRP client binary itself.

macOS and Linux take the simpler path: the EsrpCodeSigning@6
task via a shared sign.yml template. macOS in particular requires
an archive submission (useArchive: true), so centralising the
copy/zip/sign/extract cycle in the template keeps each platform
job from re-implementing it.

The Linux hosted agents do not ship with .NET, which
EsrpCodeSigning requires. UseDotNet@2 installs the .NET 8 SDK
ahead of the signing template invocation so Linux signing works
out of the box without per-platform plumbing.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Smoke tests and dry runs of the pipeline itself need to build
microsoft/git from an untagged tip, where resolve-version.sh's
tag walk would either fail or pick up an unrelated tag. Add a
queue-time `versionOverride` parameter; when it is set to
anything other than the empty string or the sentinel `-`, the
prereqs stage emits the override verbatim as the build version,
labels the tag name as `untagged`, and bypasses
resolve-version.sh entirely.

A build from an untagged commit must not race a real release
upload, so a non-empty override also forces the GitHub publishing
job off regardless of the `github` parameter, and the prereqs
step logs a warning to make that consequence visible in the run
summary.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add the per-platform Linux job that takes microsoft/git from
source to a signed `microsoft-git_<version>_<arch>.deb` staged
under `$(Build.ArtifactStagingDirectory)/_final/`. The flow
ports `create-linux-unsigned-artifacts` and
`create-linux-signed-artifacts` from the GitHub workflow at
.github/workflows/build-git-installers.yml, keeping the Make
recipe and DEBIAN/control body byte-for-byte identical so a
diff against the workflow's output is empty modulo the
deliberate departures called out below.

The GitHub workflow runs everything inside an ubuntu:20.04 /
ubuntu:22.04 container, both to pin the resulting .deb's glibc
ABI floor and to give apt-get a root-owned filesystem. The 1ES
pool images we run on
(GitClientPME-1ESHostedPool-{intel,arm64}-pc) silently ignore a
job-level `container:` directive, so the build executes on the
bare Ubuntu host VM as the unprivileged agent user. apt-get
therefore runs via `sudo`, and the job logs the Ubuntu version,
kernel, and effective UID up front so an audit can read the
.deb's effective glibc floor back from the build output.
Re-introducing a real container later (whether via 1ES's
container option, a custom container job, or docker invoked
from a step) is a separate question. The workflow's
`DEBIAN_FRONTEND=noninteractive` and `TZ=Etc/UTC` env vars
exist only to keep `tzdata` quiet inside a fresh container; the
bare 1ES image already has tzdata configured, so they are
dropped. The Node.js workaround in the workflow similarly
exists only to satisfy GitHub Actions' Node-based shim and is
not needed under Azure Pipelines.

A few intentional content changes: parallelism switches from
the workflow's hard-coded `-j5` (a runner-specific holdover) to
`-j$(nproc)`, which adapts to whatever the 1ES pool gives us;
the shell prologue changes from `set -ex` to `set -euo
pipefail` so an unbound variable or a failing stage in a pipe
aborts the job rather than silently producing a broken .deb;
`$(git_version)` now comes from the prereqs stage, dropping the
workflow's runtime dpkg-architecture round-trip in favour of
the matrix's explicit `amd64` / `arm64` entries via
`$(deb_arch)`. The `s/-rc/.rc/g` substitution carries over
because Git's GIT-VERSION-GEN spells release-candidate tags
with a dot.

The build drops its output under
`$(Build.ArtifactStagingDirectory)/app/` so the existing ESRP
signing template's `**/*.deb` pattern picks it up. A focused
move of just the signed
`microsoft-git_<version>_<arch>.deb` into
`$(Build.ArtifactStagingDirectory)/_final/` then feeds the
existing `templateContext.outputs.pipelineArtifact` for the
`linux_x64` / `linux_arm64` artifact. Naming the file
precisely turns "ESRP signed something else" into a
missing-file error rather than a silent wrong-artifact upload.

Assisted-by: Claude Opus 4.7
Co-authored-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add the per-platform macOS job that takes microsoft/git from
source to a signed-and-notarized
`git-<version>-universal.pkg` plus the corresponding `.dmg`,
both staged under `$(Build.ArtifactStagingDirectory)/_final/`.
The flow ports `create-macos-artifacts` and
`create-macos-signed-artifacts` from the GitHub workflow at
.github/workflows/build-git-installers.yml and leans on
.github/macos-installer/Makefile for the heavy lifting, but
swaps the workflow's productsign + xcrun notarytool path for
ESRP signing and ESRP MacAppNotarize.

The native Homebrew on the macOS-15-arm64 pool image is arm64
and lives at /opt/homebrew. Producing a universal binary
additionally requires the x86_64 build of gettext/libintl, so a
separate x86_64 Homebrew gets installed under /usr/local via
the upstream installer running under Rosetta and pulls gettext
from there as well. The two arch-specific libintl.a copies are
then combined with lipo into a universal archive at the
workspace root, which the upcoming config.mak's `LDFLAGS =
-L"$(pwd)"` resolves. libintl depends on iconv, but the system
/usr/lib/libiconv.dylib is already universal and exports the
`_iconv*` symbols Homebrew's gettext was built against;
Homebrew's own libiconv exports `_libiconv*` and would not
link, hence the explicit `USE_HOMEBREW_LIBICONV` /
`ICONVDIR` overrides in config.mak. Spotlight indexing on the
boot volume is disabled (`mdutil -i off /`) at the start of the
job because leaving it on caused intermittent file-locking
failures in subsequent steps.

config.mak collects the Make flags that turn on the dual-arch
compile and route around several macOS quirks:
HOST_CPU=universal, dual-arch CFLAGS (the actual
universal-binary driver), -DNO_OPENSSL for contrib Makefiles
that do not see the main Makefile's NO_OPENSSL handling, the
USE_HOMEBREW_LIBICONV / ICONVDIR overrides, gettext include
dirs from both Homebrew prefixes, and CURL_LDFLAGS /
CURL_CONFIG pinned against the OS-supplied libcurl rather than
a Homebrew copy. SKIP_DASHED_BUILT_INS disables the dashed
built-ins because on macOS the hard-link optimisation does not
kick in for the staging tree and the resulting full copies
would bloat the eventual .dmg.

`make GIT-VERSION-FILE dist dist-doc` runs in the source tree;
`git get-tar-commit-id` recovers the original commit OID from
the resulting source tarball (this becomes
GIT_BUILT_FROM_COMMIT, which the macos-installer Makefile bakes
into `git version --build-options`); the source and manpage
tarballs extract into payload/ and manpages/; a copy of
config.mak is dropped inside the extracted source so the
universal-build flags apply during the real compile; finally
`make -C .github/macos-installer payload` produces the
universal binary tree. `git get-tar-commit-id` reads only the
leading pax header and then closes its stdin, which makes
`gunzip -c` exit 141 (SIGPIPE) under the outer `set -o
pipefail`; the pipeline is wrapped in a `set +o pipefail`
subshell so the SIGPIPE does not abort the build.

The macos-installer Makefile produces the install tree at
stage/git-universal-<ver>/ but its `pkg` target packages from
build-artifacts/, so the tree is copied across after `make
payload` completes, mirroring the GitHub workflow.
GITHUB_WORKSPACE=$(Build.SourcesDirectory) is exported because
the Makefile derives BUILD_DIR from $(GITHUB_WORKSPACE), which
is unset under Azure Pipelines. XML_CATALOG_FILES points at
the catalogs from the Homebrew docbook installed in the
dependencies step. (FUTURE: the duplication exists only because
.github/macos-installer/Makefile hardcodes both DESTDIR=stage/...
and ARTIFACTDIR=build-artifacts; overriding ARTIFACTDIR on the
`make pkg` line below to point at stage/ would let us drop the
cp entirely. Worth cleaning up alongside moving the macOS
installer Makefiles out of .github/, where they live for
historical reasons rather than because they are
GitHub-specific.)

Signing happens against the install tree at
.github/macos-installer/build-artifacts/usr/local/git/, not the
source tree under payload/git-<version>/, because the
macos-installer Makefile's `pkg` target packages from
build-artifacts/; signing the source tree would have no effect
on the resulting .pkg. Following the pattern in
git-credential-manager/.azure-pipelines/release.yml, the
install tree is pre-filtered to just the Mach-O files (using
`file --mime` matching `mach`, the same heuristic
.github/scripts/codesign.sh uses), copied into a staging
directory under
$(Build.ArtifactStagingDirectory)/macos-tosign/ preserving
relative paths, handed to the existing
.azure-pipelines/esrp/sign.yml template (which zips, signs via
EsrpCodeSigning@6 with KeyCode CP-401337-Apple +
OperationCode MacAppDeveloperSign + Hardening enabled, and
extracts back into the staging dir), and finally copied back
into the install tree. The pre-filter is necessary because the
existing template's CopyFiles@2 step uses minimatch globs and
the only reliable way to pick out Mach-O files is by file
content; signing the entire install tree would either fail on
non-binary files or sign things that should not be signed
(shell scripts, perl, manpages, templates, the uninstall.sh).
UseDotNet@2 (8.x) installs the .NET SDK that EsrpCodeSigning@6
depends on, since the macOS-15-arm64 pool image does not
provide it.

The macos-installer Makefile's `pkg` target then produces an
unsigned
.github/macos-installer/disk-image/git-<version>-universal.pkg
from the signed payload tree. APPLE_INSTALLER_IDENTITY is
deliberately left undefined so pkgbuild does not try to sign;
the next step submits the .pkg back through ESRP for signing
(KeyCode CP-401337-Apple covers both Developer ID Application
and Developer ID Installer certs in this account, so
MacAppDeveloperSign on a .pkg is the productsign equivalent),
and then through ESRP for Apple notarization (MacAppNotarize,
BundleId com.git.pkg, matching the identifier pkgbuild bakes in
via `--identifier com.git.pkg` from the Makefile). The ESRP
MacAppNotarize operation handles both submission and ticket
stapling, returning the notarized .pkg back into disk-image/
via the same zip-extract template path the previous sign step
used; this is what replaces the `xcrun notarytool submit ...
--wait` plus `xcrun stapler staple` flow from
.github/scripts/notarize.sh.

Finally the Makefile's `image` target builds
.github/macos-installer/git-<version>-universal.dmg from the
contents of disk-image/. The .dmg lands at the macos-installer
root, while the signed-and-notarized .pkg lives somewhere under
disk-image/: ESRP's MacAppNotarize op repacks its output zip to
wrap the notarized .pkg in a UUID-named .zip.unzipped/
subdirectory, so depending on whether notarization ran, the
.pkg ends up either directly under disk-image/ or at
disk-image/<uuid>.zip.unzipped/git-...pkg. `find` locates it
and moves it (along with the globbed .dmg) into
$(Build.ArtifactStagingDirectory)/_final/, which the job's
templateContext.outputs already publishes as the
`macos_universal` pipeline artifact. `set -euo pipefail` means
an empty `find` result, or a missing .dmg, fails the mv loudly
rather than producing a silent half-empty upload, matching the
same defensive choice the Linux stage step makes.

Assisted-by: Claude Opus 4.7
Co-authored-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add the per-platform Windows job that takes microsoft/git from
source to ESRP-signed `Git-<version>-<arch>.exe` and
`PortableGit-<version>-<arch>.exe`, plus the matching
sha-256.txt sidecar, all staged under
$(Build.ArtifactStagingDirectory)/_final/. The flow ports
`create-windows-artifacts` and `create-windows-signed-artifacts`
from the GitHub workflow at
.github/workflows/build-git-installers.yml, leaning on
git-for-windows/build-extra (please.sh + installer/release.sh)
for the build itself.

GitHub Actions has the
git-for-windows/setup-git-for-windows-sdk@v1 action that drops
a full SDK onto the runner; Azure Pipelines has no equivalent
task, so the Windows job has to bootstrap the SDK by hand
before any of the bash-driven build steps can run. Bootstrap is
driven by .azure-pipelines/scripts/windows/setup-git-sdk.sh,
which a Bash@3 task invokes via `filePath:` so it runs under
the agent's MinGit-provided bash. A `sdk_repo` field on each
windows_matrix entry (git-for-windows/git-sdk-64 for x64,
git-sdk-arm64 for ARM64) lets the script pick the right
upstream; the script does a partial+bare clone of the SDK,
clones git-for-windows/build-extra into a sibling directory,
then runs `please.sh create-sdk-artifact --sdk=<bare>
--out=<sdk> build-installers` to materialise the
build-installers flavour of the SDK at the requested output
path. Routing through `please.sh create-sdk-artifact` keeps
the bytes flowing via plain GitHub HTTPS clones (which 1ES
allows) rather than the raw and release-asset CDNs that an
earlier download-the-snapshot approach hit. Once the SDK is in
place, its own usr/bin (which ships cygpath) and the matching
MinGW toolchain bin/ are exposed to subsequent tasks via
##vso[task.prependpath].

The arm64 Windows hosted agents do not have Azure CLI
pre-installed, which the AzureCLI@2 task in the ESRP setup step
needs. Install the x64 MSI (which runs under x86-64 emulation
on arm64 Windows) and prepend it to PATH, gated on a poolArch
condition. This is a workaround until the bug preventing us
from baking Azure CLI into the hosted pool image is fixed, at
which point this step can be dropped.

A small helper, .azure-pipelines/scripts/windows/utils.sh,
provides `to_windows_path` / `to_unix_path` for scripts running
on Windows agents. Both prefer `cygpath` when it is on PATH
and fall back to a small pure-shell parser otherwise. The
fallback matters because some bash steps run before the SDK is
bootstrapped and only have MinGit's bash available, which does
not ship cygpath. Both setup-git-sdk.sh and the ESRP-sign
script source utils.sh rather than duplicating the conversion
logic.

Git for Windows' build tooling (please.sh, signtool.sh, the
installer .iss templates, and the MINGW-packages helpers)
lives in git-for-windows/build-extra rather than in the SDK
snapshot. The GitHub workflow's Windows job clones it into
/usr/src/build-extra of the SDK before invoking please.sh;
this job does the same. A partial clone (--filter=blob:none)
plus --single-branch -b main is enough for everything please.sh
needs and avoids pulling the full blob history, matching the
workflow's invocation byte for byte.

The mingw-w64-git package is built via please.sh
build-mingw-w64-git from a Bash@3 task using the SDK's bash
that the bootstrap put on PATH. Outputs land in
$(Build.SourcesDirectory)/artifacts/ so the subsequent
installer-build step can pass them to please.sh
make_installers_from_mingw_w64_git via --pkg= flags. Three
small adaptations from the GitHub workflow's source step are
worth flagging. First, the /usr/bin/git trampoline that
delegates to the matching MinGW-built git.exe is the same one
the workflow writes by hand; makepkg-mingw shells out to plain
`git`, and the SDK bash's git candidates would otherwise come
from MinGit, not the toolchain we are building against. Second,
the user.name / user.email / PACKAGER values are hardcoded to
a build-bot identity since Azure Pipelines has no GitHub-actor
equivalent. Third, please.sh's --only-<arch> flag takes the
bare CPU name (x86_64 or aarch64), not the toolchain triple,
so a `cpu_arch` matrix dimension surfaces the right value next
to each toolchain entry.

The build task detaches stdin via `exec </dev/null` before
invoking makepkg-mingw, because pacman's git-extra
post_install hook runs `for s in $(grep -l PAT $(find
/mingw*/bin/ ...))` and falls back to reading stdin when
/mingw*/bin/ is absent and find produces empty output. Bash@3
leaves the task's stdin pipe open with no writer (the GitHub
Actions runner closes it for the same reason; see
actions/runner ProcessInvoker.cs), so without the detach the
build would hang indefinitely waiting for input. Two pieces of
the workflow's pkg-build step are intentionally not ported and
are noted in a comment for follow-up: the per-tarball GPG
signing (replaceable with an ESRP PGP operation analogous to
the Linux LinuxSign flow if needed downstream), and the
MINGW-packages bundle creation, which depends on a
PKGBUILD.<tag_name> snapshot that microsoft/git does not
currently ship.

A single bash task drives please.sh
make_installers_from_mingw_w64_git for both installer and
portable variants. The GitHub workflow runs these as separate
matrix jobs (one per type/arch combination); keeping both
builds in the same Azure Pipelines job means the .pkg.tar.*
artifacts produced by the previous step are available without
an inter-job artifact passing trip. The PDB archive copy into
build-extra/cached-source-packages is the same prerequisite
that --include-pdbs needs in the GitHub workflow. The --pkg=
filter that strips signatures and the optional archimport /
cvs / p4 / gitweb / doc-man pieces matches the workflow's sed
exactly so the resulting .exe sizes are comparable. The same
`exec </dev/null` stdin detach as the build mingw-w64-git step
applies. To make a silent hang inside the nested pacman /
makepkg / Inno Setup chain debuggable, BASH_ENV is pointed at
a tiny `set -x` fragment so every nested non-interactive bash
subshell auto-enables xtrace, and please.sh itself is invoked
via `sh -x`. The trace volume is the cost we pay for being
able to identify which step a future hang is stuck in; cheap
compared to debugging a silent hang.

The GitHub Actions workflow applies five `sed` transformations
during the Windows build to turn upstream Git for Windows into
the microsoft/git distribution. They are spread over five run:
blocks and largely opaque without following each sed pattern
by hand. Capture them as patches under
.azure-pipelines/patches/ instead, grouped by the upstream tree
they mutate (build-extra/* for installer customisation,
git-sdk/* for the SDK's git-update-git-for-windows helper);
the patches themselves carry the explanatory commit-style
headers each transformation deserves. A small helper,
.azure-pipelines/scripts/apply-patches.sh, applies every
*.patch in a directory in lexicographic order via `patch -p1`.
patch(1) is used rather than `git apply` because the latter is
strict about context whitespace; CRLF/LF mismatches between
the patch context (as authored) and the working tree (which
may be CRLF on Windows checkouts) trip it up. patch is more
forgiving by default, and matches the convention used by
msys2/MINGW-packages PKGBUILDs and
git-for-windows/build-extra's get-sources.sh. The patches
apply against /usr/src/build-extra and against the SDK's
/$(mingwprefix), between cloning build-extra and building the
mingw-w64-git package, mirroring where the GitHub workflow's
sed steps slot in.

ESRP signing is wired through build-extra's `signtool` alias
hook so that ESRP signs every binary that ends up inside the
Windows installer and portable Git, not just the outer .exe
wrapper. A naive post-build sign of just `Git-*.exe` and
`PortableGit-*.exe` would leave every binary embedded inside
the installer (DLLs, helper exes, the mingw-w64-git pkg
payload) shipping unsigned. The mechanism, set up by
build-extra: please.sh's build_mingw_w64_git checks `git
config alias.signtool` and, if set, exports `SIGNTOOL="git ...
signtool"` into makepkg-mingw so the PKGBUILD can sign
individual binaries during the package build; build-extra's
installer/release.sh checks the same alias and passes
`//Ssigntool="git signtool $f" //DSIGNTOOL` to Inno Setup's
ISCC.exe, which then signs every embedded file via the
SignTool=signtool directive in install.iss. The portable Git
.exe is a 7z self-extractor that bypasses the Inno Setup
signtool path, so it is signed explicitly after
`make_installers` returns.

The ESRP setup template (which sets ESRP_TOOL and ESRP_AUTH)
runs before the build steps; a Bash@3 task registers the
signtool alias to invoke
.azure-pipelines/esrp/windows/esrpsign.sh; the ESRP env vars
are added to both build tasks so esrpsign.sh has ESRP_TOOL,
ESRP_AUTH, and SYSTEM_ACCESSTOKEN available when invoked via
the alias from inside please.sh, makepkg-mingw, or Inno Setup.
MSYSTEM is exported in the build installer task's env block
because installer/release.sh requires it to select the
architecture branch, and Bash@3 does not source /etc/profile.
esrpsign.sh's calling convention (`<file> [file ...]`, sign in
place) already matches signtool.sh's, so no further script
changes are needed.

Stage `Git-*.exe` and `PortableGit-*.exe` alongside a SHA-256
sidecar into $(Build.ArtifactStagingDirectory)/_final/, which
the job's templateContext.outputs.pipelineArtifact already
publishes as the `windows_x64` / `windows_arm64` artifact.
The SHA-256 sidecar is computed here, post-build, rather than
in the build step because ESRP signing rewrites the .exe
contents; a SHA-256 computed before signing would mismatch the
bytes that ship.

Assisted-by: Claude Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
With build, signing, notarization, validation, and
draft-release publishing all in place, the Azure Pipeline is
ready to take over from the GitHub Actions
build-git-installers workflow. Switch `trigger: none` to a
tag-only trigger matching the same `v[0-9]*vfs*` pattern the
GitHub workflow used (and that resolve-version.sh validates
against), and explicitly exclude all branches so the pipeline
does not fire on every topic-branch push.

Flip the `esrp` and `github` parameter defaults from false to
true. The GitHub release job still uses `isDraft: true`, so a
maintainer inspects and publishes the release manually; manual
runs in the Azure DevOps UI can still uncheck either box for a
dry run.

Assisted-by: Claude Opus 4.7
Co-authored-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
release: create initial Windows installer build workflow

Add a manual-only GitHub Actions workflow for building the
Windows installer (x86_64 plus portable Git), driven via
`workflow_dispatch:`. The production release path for the
official microsoft/git installers lives in
.azure-pipelines/release.yml; this workflow is kept around as a
fallback so the Windows installer can still be produced on
demand for debugging or comparison.

The build steps are pinned to `windows-2019` (rather than
`windows-latest`) to ensure the correct Visual Studio version
is used (verified in the pipeline via `type -p mspdb140.dll`),
and the SDK used is the `full` flavor rather than
`build-installers` due to a known (but not-yet-fixed) issue
downloading the `build-installers` flavor with the
`git-for-windows/setup-git-for-windows-sdk` Action.

There is no code-signing certificate available to this workflow,
so the artifacts it produces are unsigned and must not be
published as releases; they are useful only for build-time
debugging.

Signed-off-by: Victoria Dye <vdye@github.com>
Assisted-by: Claude Opus 4.7
Co-authored-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho dscho self-assigned this May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants